﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace MSI2
{
    public class PhdSec
    {
        private const string DefaultFilename1 = "firstLayerNetwork.dat";
        private const string DefaultFilename2 = "secondLayerNetwork.dat";

        //private readonly int NumberOfAminoAcids;
        private int _firstLevelWindowSize;
        private int _secondLevelWindowSize;
        private int _outputSize;

        private NeuralNetwork _firstLevelNetwork;
        private NeuralNetwork _secondLevelNetwork;
        private double[] _secondLevelOutput;

        public PhdSec(NeuralNetwork.NeuralNetworkParams firstLevelNetworkParams, NeuralNetwork.NeuralNetworkParams secondLevelNetworkParams)
        {
            _firstLevelNetwork = new NeuralNetwork(firstLevelNetworkParams);
            _secondLevelNetwork = new NeuralNetwork(secondLevelNetworkParams);
        }

        //TODO: dodatkowe parametry dla sieci
        public void Run(bool isTraining, double[] amino, double[] structure, int aminoLength, int firstLevelWindowSize = 13, int secondLevelWindowSize = 17, int outputSize = 3)
        {
            var firstLevelOutput = new double[3 * aminoLength];
            _secondLevelOutput = new double[3 * aminoLength];
            _firstLevelWindowSize = firstLevelWindowSize;
            _secondLevelWindowSize = secondLevelWindowSize;
            _outputSize = outputSize;

            for (int i = _firstLevelWindowSize / 2; i < aminoLength - _firstLevelWindowSize / 2; i++)
            {
                double[] window = GetWindowAt(amino, (i - _firstLevelWindowSize / 2) * 21, _firstLevelWindowSize * 21);
                if (isTraining)
                    _firstLevelNetwork.Backpropagation(window, new double[] { structure[i * 3], structure[i * 3 + 1], structure[i * 3 + 2] });
                else 
                    _firstLevelNetwork.RunNetwork(window);
                firstLevelOutput[i * 3] = _firstLevelNetwork.OutputLayer[0];
                firstLevelOutput[i * 3 + 1] = _firstLevelNetwork.OutputLayer[1];
                firstLevelOutput[i * 3 + 2] = _firstLevelNetwork.OutputLayer[2];
            }

            for (int i = _secondLevelWindowSize / 2; i < aminoLength - _secondLevelWindowSize / 2; i++)
            {
                double[] window = GetWindowAt(firstLevelOutput, (i - _secondLevelWindowSize / 2) * 3, _secondLevelWindowSize * 3);
                if (isTraining)
                    _secondLevelNetwork.Backpropagation(window, new double[] { structure[i * 3], structure[i * 3 + 1], structure[i * 3 + 2] });
                else 
                    _secondLevelNetwork.RunNetwork(window);
                _secondLevelOutput[i * 3] = _secondLevelNetwork.OutputLayer[0];
                _secondLevelOutput[i * 3 + 1] = _secondLevelNetwork.OutputLayer[1];
                _secondLevelOutput[i * 3 + 2] = _secondLevelNetwork.OutputLayer[2];
            }
        }

        private static double[] GetWindowAt(IEnumerable<double> sequence, int begin, int count)
        {
            var ret = sequence.Where((elem, index) => index >= begin && index < begin + count).ToArray();
            return ret;
        }

        public double[] GetResult()
        {
            return _secondLevelOutput;
        }

        public double ComputeScore(double[] structure, ref int correctAns, ref int totalAns)
        {
            int correct = 0;
            for (int i = 0; i < _secondLevelOutput.Length; i+=3)
            {
                int maxInd = -1;
                double maxVal = 0;

                if (_secondLevelOutput[i] > maxVal)
                {
                    maxVal = _secondLevelOutput[i];
                    maxInd = i;
                }

                if (_secondLevelOutput[i+1] > maxVal )
                {
                    maxVal = _secondLevelOutput[i + 1];
                    maxInd = i + 1;

                }

                if (_secondLevelOutput[i + 2] > maxVal)
                {
                    maxVal = _secondLevelOutput[i + 2];
                    maxInd = i + 2;
                }

                //switch (maxInd % 3)
                //{
                //    case 0: Console.WriteLine("H"); break;
                //    case 1: Console.WriteLine("E"); break;
                //    case 2: Console.WriteLine("L"); break;
                //}

                if (maxInd != - 1 && structure[maxInd] == 1)
                {
                    correct++;
                }
            }
            var ret = (double)300*correct/(structure.Length-48);

            correctAns += correct;
            totalAns += (structure.Length - 48)/3;

            return Double.IsNaN(ret) ? 0 : ret;
        }

        public void Save(string firstLayerFile, string secondLayerFile)
        {
            NeuralNetwork.SaveNeuralNetwork(firstLayerFile, _firstLevelNetwork);
            NeuralNetwork.SaveNeuralNetwork(secondLayerFile, _secondLevelNetwork);
        }

        public void Save()
        {
            Save(DefaultFilename1, DefaultFilename2);
        }

        public void Load(string firstLayerFile, string secondLayerFile)
        {
            _firstLevelNetwork = NeuralNetwork.LoadNeuralNetwork(firstLayerFile);
            _secondLevelNetwork = NeuralNetwork.LoadNeuralNetwork(secondLayerFile);
        }

        public void Load()
        {
            Load(DefaultFilename1, DefaultFilename2);
        }

        private string GetStructure()
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("        ");

            for (int i = 0; i < _secondLevelOutput.Length; i += 3)
            {
                int maxInd = -1;
                double maxVal = 0;

                if (_secondLevelOutput[i] > maxVal)
                {
                    maxVal = _secondLevelOutput[i];
                    maxInd = i;
                }

                if (_secondLevelOutput[i + 1] > maxVal)
                {
                    maxVal = _secondLevelOutput[i + 1];
                    maxInd = i + 1;

                }

                if (_secondLevelOutput[i + 2] > maxVal)
                {
                    maxInd = i + 2;
                }

                switch(maxInd % 3)
                {
                    case 0: stringBuilder.Append("H"); break;
                    case 1: stringBuilder.Append("E"); break;
                    case 2: stringBuilder.Append("L"); break;
                }
            }

            stringBuilder.Append("        ");

            return stringBuilder.ToString();
        }

        public void SaveOutput(ParsedData data, StreamWriter streamWriter)
        {
            streamWriter.WriteLine(data.SequenceString);
            streamWriter.WriteLine(data.StructureString);
            streamWriter.WriteLine(GetStructure());
            streamWriter.WriteLine();
        }
    }
}
